package com.astoolkit.workflow
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	import flash.utils.setTimeout;
	
	import mx.events.PropertyChangeEvent;
	import mx.events.PropertyChangeEventKind;
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.rpc.Fault;
	import mx.utils.UIDUtil;

	[Event(
		name="started",
		type="com.astoolkit.workflow.WorkflowEvent")]
	[Event(
		name="warning",
		type="com.astoolkit.workflow.WorkflowEvent")]
	[Event(
		name="fault",
		type="com.astoolkit.workflow.WorkflowEvent")]
	[Event(
		name="completed",
		type="com.astoolkit.workflow.WorkflowEvent")]
	[Event(
		name="progress",
		type="com.astoolkit.workflow.WorkflowEvent")]
	[Event(
		name="prepare",
		type="com.astoolkit.workflow.WorkflowEvent")]

	[Bindable]
	public class BaseTask 
		extends EventDispatcher
		implements IWorkflowTask
	{
		private static const LOGGER : ILogger = 
			Log.getLogger( getQualifiedClassName( BaseTask ).replace(/:+/g, "." ) );
		
		private var _root : IWorkflow;
		
		protected var _parent : IWorkflow;
		protected var _name : String= "unnamed";
		protected var _enabled : Boolean = true;
		protected var _failurePolicy : String = FailurePolicy.ABORT;
		protected var _currentProgress : Number = -1;
		protected var _delay : int;
		protected var _delegate : IWorkflowDelegate;
		protected var _status : String;
		
		private var _resumeCallBacks : Array;
		
		private var _suspendableListeners : Object;
		
		public function get status():String
		{
			return _status;
		}
		
		public function get title():String
		{
			return _name;
		}
		
		public function set title(inName:String):void
		{
			_name = inName;
		}
		
		public function get delay():int
		{
			return _delay;
		}
		
		public function set delay(inDelay:int):void
		{
			_delay = inDelay;
		}
		
		public function get enabled():Boolean
		{
			return _enabled;
		}
		
		public function set enabled(inEnabled:Boolean):void
		{
			_enabled = inEnabled;
		}
		
		
		[Inspectable(defaultValue="abort", enumeration="abort,warn,ignore")]
		public function get failurePolicy() : String
		{
			return _failurePolicy;
		}
		
		public function set failurePolicy( inPolicy : String ) : void
		{
			if( inPolicy != FailurePolicy.ABORT && inPolicy != FailurePolicy.IGNORE && inPolicy != FailurePolicy.WARN )
			{
				throw new Error( 
					"Workflow 'failurePolicy' property can only be either 'abort' or 'warn' or 'ignore': " +
					"found '" + inPolicy + "'" );
			}
			_failurePolicy = inPolicy;
		}

		public function get parent():IWorkflow
		{
			return _parent;
		}
		
		public function set parent(inParent:IWorkflow):void
		{
			_parent = inParent;
		}
		
		public function get currentProgress():Number
		{
			return _currentProgress;
		}
		
		public function set currentProgress(inProgress:Number):void
		{
			_currentProgress = inProgress;
		}
		
		public function set delegate(inDelegate:IWorkflowDelegate):void
		{
			_delegate = inDelegate;
		}
		
		public function cleanUp():void
		{
			_delegate.context.removeEventListener( WorkflowEvent.SUSPENDED, onContextSuspended );
			_delegate.context.removeEventListener( WorkflowEvent.RESUMED, onContextResumed );
			_suspendableListeners = null;
		}
		
		public function begin():void
		{	
			if( _status == TaskStatus.RUNNING )
				throw new Error( "begin() called while task already running: " + getQualifiedClassName( this ) );
			_delegate.context.addEventListener( WorkflowEvent.SUSPENDED, onContextSuspended );
			_delegate.context.addEventListener( WorkflowEvent.RESUMED, onContextResumed );
			_status = TaskStatus.RUNNING;
			_delegate.onBegin( this );
		}

		public function get root() : IWorkflow
		{
			if( _root )
				return _root;
			var out : IWorkflowTask = this;
			while( out.parent != null )
			{
				out = out.parent;
			}
			_root = out as IWorkflow;
			return _root;
		}
		
		protected function setProgress( inValue : Number ) : void
		{
			var oldVal : Number = _currentProgress;
			_currentProgress = inValue;
			if( _currentProgress >= 0 && _currentProgress <= 1 )
			{
				_delegate.onProgress( this );
				dispatchEvent( 
					new PropertyChangeEvent(
						PropertyChangeEvent.PROPERTY_CHANGE,
						false, 
						true, 
						PropertyChangeEventKind.UPDATE, 
						"progress", 
						oldVal, 
						_currentProgress, 
						this) );
			}
			_delegate.onProgress( this );
		}
		
		protected function complete() : void
		{
			setTimeout( _deferredComplete, 10 );
		}
		
		protected function _deferredComplete() : void
		{
			if( _status == TaskStatus.SUSPENDED )
			{
				addResumeCallBack( _deferredComplete );
				return;
			}
			_status = TaskStatus.IDLE;
			_delegate.onComplete( this );
		}

		protected function fail( inMessage : String, inFault : Fault = null ) : void
		{
			setTimeout( _deferredFail, 10, inMessage, inFault );
		}

		protected function _deferredFail( inMessage : String, inFault : Fault = null ) : void
		{
			if( _status == TaskStatus.SUSPENDED )
			{
				addResumeCallBack( 
					function() : void
					{
						_deferredFail( inMessage, inFault ); 
					} );
				return;
			}
			_status = TaskStatus.IDLE;
			_delegate.onFault( this, inMessage, inFault );
		}
			
		
		protected function addResumeCallBack( inFunction : Function ) : void
		{
			if( !_resumeCallBacks )
				_resumeCallBacks = [];
			_resumeCallBacks.push( inFunction );
		}
		
		protected function invokeResumeCallBacks() : void
		{
			if( _resumeCallBacks != null )
			{
				for each( var fn : Function in _resumeCallBacks )
				{
					fn();
				}
				clearResumeCallBack();
			}
		}
		
		protected function clearResumeCallBack() : void
		{
			_resumeCallBacks = null;
		}
		
		public function prepare() : void
		{
			_delegate.onPrepare( this );
			_suspendableListeners = {};
		}
		
		public function abort() : void
		{
			_status = TaskStatus.IDLE;
			_delegate.onAbort( this, "Aborted: " + title );
		}
		
		public function suspend() : void
		{
			if( _status != TaskStatus.SUSPENDED )
			{
				_status = TaskStatus.SUSPENDED;
				_delegate.onSuspend( this );
			}
		}
		
		public function resume() : void
		{
			if( _status != TaskStatus.RUNNING )
			{
				_status = TaskStatus.RUNNING;
				_delegate.onResume( this );
				invokeResumeCallBacks();
			}
		}
		
		protected function onContextSuspended( inEvent : WorkflowEvent ) : void
		{
			if( _status == TaskStatus.RUNNING )
			{
				suspend();
			}
		}
		
		protected function onContextResumed( inEvent : WorkflowEvent ) : void
		{
			if( status == TaskStatus.SUSPENDED )
			{
				resume();
			}
		}
		
		protected function addSuspendableListener( 
			inTarget : IEventDispatcher, 
			inType : String, 
			inHandler : Function, 
			inUseCapture : Boolean = false, 
			inPriority : uint = 0, 
			inUseWeakReference : Boolean = false ) : void
		{
			inTarget.addEventListener( inType, suspendableEventHandler, inUseCapture, inPriority, inUseWeakReference );
			if( !_suspendableListeners.hasOwnProperty( UIDUtil.getUID( inTarget ) + inType ) )
				_suspendableListeners[ UIDUtil.getUID( inTarget ) + inType ] = [];
			_suspendableListeners[ UIDUtil.getUID( inTarget ) + inType ].push( inHandler );
		}
		
		protected function removeSuspendableListener( 
			inTarget : IEventDispatcher, 
			inType : String, 
			inHandler : Function ) : void
		{
			inTarget.removeEventListener( inType, suspendableEventHandler);
			if( !_suspendableListeners.hasOwnProperty( UIDUtil.getUID( inTarget ) + inType ) )
				_suspendableListeners[ UIDUtil.getUID( inTarget ) + inType ] = [];
			_suspendableListeners[ UIDUtil.getUID( inTarget ) + inType ].push( inHandler );
		}
		
		private function suspendableEventHandler( inEvent : Event ) : void
		{
			var fns : Array = _suspendableListeners[ UIDUtil.getUID( inEvent.target ) + inEvent.type ] as Array;
			if( fns == null )
				return;
			for each( var fn : Function in fns )
			{
				if( _status == TaskStatus.SUSPENDED )
				{
					addResumeCallBack( 
						function() : void
						{
							fn( inEvent );
						} );
				}
				else
				{
					fn( inEvent );
				}
			}
		}
				
	}
}
